// Fluxions 3D Engine
// shaderprogram.hpp
// Copyright (C) 2000-2011 Jonathan Metzgar
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#ifndef FLUXIONS_SHADERPROGRAM_HPP
#define FLUXIONS_SHADERPROGRAM_HPP

#include <string>
#include <vector>
#include <map>
#include "opengl.hpp"
#include "utils.hpp"
#include "math.hpp"

namespace Fluxions
{
	class ShaderProgram
	{
	public:
		ShaderProgram();
		~ShaderProgram();

		bool Create();
		void Delete();
		void Use();
		bool Link();
		std::string GetLastErrorString();
		std::string GetInfoLogString(GLuint shaderObj);
		bool AttachShader(GLenum type, const std::string &name, const std::string &filename);
		void DetachShader(const std::string &name);
		bool QuickCreate(const std::string &vertexShaderFilename, const std::string &fragmentShaderFilename);
		int GetUniformIndex(const char *uniformName);
		int GetUniformIndex(const std::string &uniformName);
		void UniformVector3f(const std::string &uniformName, const Vector4f &vector);
		void UniformVector4f(const std::string &uniformName, const Vector4f &vector);
		void UniformMatrix4f(const std::string &uniformName, const Matrix4f &matrix);
	private:
		GLuint programId;
		std::string errorString;
		std::map<std::string, GLuint> shaderMap;
		std::map<std::string, int> uniformMap;
	};
	
	ShaderProgram::ShaderProgram()
	: programId(0)
	{
		
	}
	
	ShaderProgram::~ShaderProgram()
	{
		
	}
	
	bool ShaderProgram::Create()
	{
		programId = glCreateProgram();
		if (programId == 0)
			return false;
		return true;
	}
	
	void ShaderProgram::Delete()
	{
		if (programId != 0)
		{
			glDeleteProgram(programId);
			std::map<std::string, GLuint>::iterator it;
			for (it = shaderMap.begin(); it != shaderMap.end(); ++it)
			{
				glDeleteShader(it->second);
			}
			shaderMap.clear();
		}
	}
	
	void ShaderProgram::Use()
	{
		glUseProgram(programId);
	}
	
	bool ShaderProgram::Link()
	{
		GLint status;
		uniformMap.clear();
		glLinkProgram(programId);
		glGetProgramiv(programId, GL_LINK_STATUS, &status);
		if (status == GL_FALSE)
		{
			errorString = "Error Linking";
			return false;
		}
		// rebuild uniforms
		int numActiveUniforms;
		char *buffer;
		int length;
		glGetProgramiv(programId, GL_ACTIVE_UNIFORMS, &numActiveUniforms);
		glGetProgramiv(programId, GL_ACTIVE_UNIFORM_MAX_LENGTH, &length);
		if (length == 0)
			return true;
		buffer = (char *)malloc(length);
		if (!buffer)
			return false;
		for (int i = 0; i < numActiveUniforms; ++i)
		{
			glGetActiveUniform(programId, i, length, NULL, NULL, NULL, buffer);
			uniformMap[string(buffer)] = i;
		}
		free(buffer);
		return true;
	}
	
	std::string ShaderProgram::GetLastErrorString()
	{
		return errorString;
	}
	
	std::string ShaderProgram::GetInfoLogString(GLuint shaderObj)
	{
		std::string str;
		
		int infoLogLength;
		char *infoLog;
		glGetShaderiv(shaderObj, GL_INFO_LOG_LENGTH, &infoLogLength);
		if (infoLogLength == 0)
			return "";
		infoLog = new char[infoLogLength];
		glGetShaderInfoLog(shaderObj, infoLogLength, NULL, infoLog);
		str = infoLog;
		delete [] infoLog;
		return str;
	}
	
	bool ShaderProgram::AttachShader(GLuint type, const std::string &name, const std::string &filename)
	{
		std::string source;
		source = LoadEntireTextFile(filename);
		GLuint shaderId;
		shaderId = glCreateShader(type);
		if (shaderId == 0)
			return false;
		const GLchar *sourcePtr = source.c_str();
		glShaderSource(shaderId, 1, &sourcePtr, NULL);
		glCompileShader(shaderId);
		GLint status;
		glGetShaderiv(shaderId, GL_COMPILE_STATUS, &status);
		cout << "Shader Log(" << filename << "): " << GetInfoLogString(shaderId) << endl;
		if (status == GL_FALSE)
		{
			glDeleteShader(shaderId);
			return false;
		}
		glAttachShader(programId, shaderId);
		shaderMap[name] = shaderId;
		return true;
	}
	
	void ShaderProgram::DetachShader(const std::string &name)
	{
		std::map<std::string, GLuint>::iterator it;
		it = shaderMap.find(name);
		if (it == shaderMap.end())
			return;
		else
			shaderMap.erase(it);
	}
	
	bool ShaderProgram::QuickCreate(const std::string &vertexShaderFilename, const std::string &fragmentShaderFilename)
	{
		Delete();
		Create();
		if (!AttachShader(GL_VERTEX_SHADER, StripFileExtension(vertexShaderFilename), vertexShaderFilename))
		{
			Delete();
			return false;
		}
		if (!AttachShader(GL_FRAGMENT_SHADER, StripFileExtension(fragmentShaderFilename), fragmentShaderFilename))
		{
			Delete();
			return false;
		}
		return Link();
	}
	
	int ShaderProgram::GetUniformIndex(const char *uniformName)
	{
		int index = -1;
		std::map<std::string, int>::iterator it;
		it = uniformMap.find(uniformName);
		if (it != uniformMap.end())
			return it->second;
		return index;
	}
	
	int ShaderProgram::GetUniformIndex(const std::string &uniformName)
	{
		int index = -1;
		std::map<std::string, int>::iterator it;
		it = uniformMap.find(uniformName);
		if (it != uniformMap.end())
			return it->second;
		return index;
	}

	void ShaderProgram::UniformVector3f(const std::string &uniformName, const Vector4f &vector)
	{
		int index = GetUniformIndex(uniformName);
		if (index < 0)
			return;
		glUniform3fv(index, 1, vector.v);
	}

	void ShaderProgram::UniformVector4f(const std::string &uniformName, const Vector4f &vector)
	{
		int index = GetUniformIndex(uniformName);
		if (index < 0)
			return;
		glUniform4fv(index, 1, vector.v);
	}

	void ShaderProgram::UniformMatrix4f(const std::string &uniformName, const Matrix4f &matrix)
	{
		int index = GetUniformIndex(uniformName);
		if (index < 0)
			return;
		glUniformMatrix4fv(index, 1, GL_FALSE, matrix.m);
	}
}

#endif
